﻿//  Copyright © Decebal Mihailescu 2013
//  All rights reserved. This source code and binaries are distributed under 
//  Microsoft Public License (Ms-PL)
//  The latest code is available at http://VSDesignPatterns.codeplex.com

//  Redistributions of source code must retain the above copyright notice, 
//  this list of conditions and the following disclaimer. 
//  Redistributions in binary form must reproduce the above copyright notice, 
//  this list of conditions and the following disclaimer in the documentation 
//  and/or other materials provided with the distribution. 

//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE. IT CAN BE DISTRIBUTED FREE OF CHARGE AS LONG AS THIS HEADER 
//  REMAINS UNCHANGED.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using System.Globalization;
using System.Diagnostics;

namespace DesignPatternsWizard
{
    using Common;
    using Ionic.Zip;

    public class TemplateArgument : ITemplateArgument
    {
        ITemplatePattern _iParentTemplate;
        public TemplateArgument(ITemplatePattern iparentTemplate, XElement xmlArgument)
        {
            _iParentTemplate = iparentTemplate;
            if (xmlArgument == null || xmlArgument.Name != "TemplateArgument")
                return;
            if (xmlArgument.Attribute("ArgumentName") != null)
                ArgumentName = xmlArgument.Attribute("ArgumentName").Value;
            if (xmlArgument.Attribute("Description") != null)
                Description = xmlArgument.Attribute("Description").Value;
            Value = xmlArgument.Value;

        }
        #region IServiceProvider Members

        public object GetService(Type serviceType)
        {
            if (serviceType == typeof(ITemplateArgument) || serviceType == typeof(IServiceProvider))
                return this;
            object res = null;
            if (serviceType == typeof(ITemplatePattern))
                res = (_iParentTemplate == null) ? null : _iParentTemplate.GetService(serviceType);
            return res;
        }

        #endregion
        #region ITemplateArgument Members
        public string ArgumentName { get; private set; }
        public string Description { get; private set; }
        public string Value { get; set; }
        public bool IsRuntime { get; private set; }
        #endregion


    }
    public class TemplateRuntimeArgument : ITemplateArgument
    {
        ITemplatePattern _parentTemplate;
        IProjectInfo _projectInfo;
        public TemplateRuntimeArgument(ITemplatePattern parentTemplate, IProjectInfo projectInfo, XElement xmlArgument)
        {
            _parentTemplate = parentTemplate;
            _projectInfo = projectInfo;
            if (xmlArgument == null || xmlArgument.Name != "TemplateArgument")
                return;
            if (xmlArgument.Attribute("ArgumentName") != null)
                ArgumentName = xmlArgument.Attribute("ArgumentName").Value;
            if (xmlArgument.Attribute("Description") != null)
                Description = xmlArgument.Attribute("Description").Value;
            string val;
            if (projectInfo.ProjectProperties.TryGetValue(ArgumentName, out val))
                Value = val;
        }
        #region IServiceProvider Members

        public object GetService(Type serviceType)
        {
            if (serviceType == typeof(TemplateRuntimeArgument) || serviceType == typeof(IServiceProvider))
                return this;
            object res = null;
            if (serviceType == typeof(IProjectInfo))
                res = (_projectInfo == null) ? null : _projectInfo.GetService(serviceType);
            if (res != null)
                return res;
            res = (_parentTemplate == null) ? null : _parentTemplate.GetService(serviceType);
            return res;
        }

        #endregion
        #region ITemplateArgument Members
        public string ArgumentName { get; private set; }
        public string Description { get; private set; }
        public string Value { get; set; }
        public bool IsRuntime { get; private set; }
        #endregion


    }
    /*
    public interface ITemplateFile : IServiceProvider
    {
        string FileItemName { get; }
        string FileTemplate { get; }
        string StoredAs { get; }
        string FileTemplateContent { get; }

    }
    */
    public class TemplateFile : ITemplateFile
    {

        ITemplatePattern _iParentTemplate;

        FileData GeneratePatternFile(IProjectInfo projectInfo, TemplateFile templateFile)
        {
            return null;
        }
        IRepository _iRepository;
        public TemplateFile(ITemplatePattern iParentTemplate, XElement fileElement)
        {
            _iParentTemplate = iParentTemplate;
            _iRepository = _iParentTemplate.GetAsService<IRepository>();
            ILanguageProviderMetaData ilpm = _iParentTemplate.GetAsService<ILanguageProviderMetaData>();
            StoredAs = (ilpm == null) ? StorageType.FileSystem : ilpm.SavedAs;
           // StoredAs = _iParentTemplate.DefaultStorage;
            if (fileElement == null)
                return;
            switch (fileElement.Name.LocalName)
            {
                case "FileTemplate":
                    FileItemName = fileElement.Attribute("FileItemName").Value;
                    FileTemplate = fileElement.Value;
                    //if (fileElement.Attribute("StoredAs") != null)
                    //StoredAs = (StorageType)Enum.Parse(typeof(StorageType),fileElement.Attribute("StoredAs").Value, true);
                    StoredAs = fileElement.Attr2Enum<StorageType>("StoredAs", StoredAs);
                    break;
                default: return;
            }
              //LoadTFContent();
            _iRepository.LoadTFContent(this);
        }
        /*
        private void LoadTFContent()
        {
            try
            {
                //string path = GetCodeFilePath();
                string path = _iRepository.GetCodeFilePath(this);
                switch (StoredAs)
                {
                    case StorageType.FileSystem:
                        if (!File.Exists(path))
                            return;
                        TextReader tr = File.OpenText(path);
                        FileTemplateContent = tr.ReadToEnd();
                        tr.Close();
                        break;
                    case StorageType.Zip:
                        //string location = GetCodeFilePath();
                        Stream str = _iParentTemplate.GetStreamFromLocation(path, StorageType.Zip);
                        if (str == null)
                            return;
                        // convert stream to string
                        StreamReader reader = new StreamReader(str);
                        FileTemplateContent = reader.ReadToEnd();
                        reader.Close();
                        str.Close();
                        break;
                    default:
                        break;
                }
            }
            catch //(Exception ex)
            {
            }
        }
        */
        /*
        private string GetCodeFilePath()
        {

            if (Path.IsPathRooted(FileTemplate))
                return FileTemplate;
            ILanguageProvider lp = _iParentTemplate.GetAsService<ILanguageProvider>();
            IProjectInfo pi = _iParentTemplate.GetAsService<IProjectInfo>();
            CodeLangEnum lang = pi.CodeLang;
            string codeFolder = null;
            switch (lang)
            {
                case CodeLangEnum.ManagedCpp:
                case CodeLangEnum.NativeCpp:
                    codeFolder = "Cpp";
                    break;
                default:
                    codeFolder = lang.ToString();
                    break;

            }
            string path = null;
            switch (StoredAs)
            {
                case StorageType.FileSystem:
                    IRepository irep = lp.GetAsService<IRepository>();
                    path = Path.Combine(irep.TemplatesFolder, string.Format("{0}\\{1}\\{2}",
                _iParentTemplate.PatternName, codeFolder, FileTemplate));
                    break;
                case StorageType.Zip:
                    ILanguageProviderMetaData ilpmd = this.GetAsService<ILanguageProviderMetaData>();
                    path =  string.Format("{3}/{0}/{1}/{2}",
                _iParentTemplate.PatternName, codeFolder, FileTemplate, ilpmd.LPRepositoryName);
                    break;

            }
            return path;

        }
        */
        public string FileItemName { get; set; }
        public string FileTemplate { get; set; }
        public StorageType StoredAs { get; private set; }
        public string FileTemplateContent { get; set; }
        #region IServiceProvider Members

        public object GetService(Type serviceType)
        {
            if (serviceType == typeof(ITemplateFile) || serviceType == typeof(IServiceProvider))
                return this;
            object res = null;
            res = (_iParentTemplate == null) ? null : _iParentTemplate.GetService(serviceType);
            return res;
        }



        #endregion
    }

    public class TemplatePattern : IServiceProvider, ITemplatePattern
    {
        //readonly static string _currentCulture = System.Windows.Forms.InputLanguage.CurrentInputLanguage.Culture.ThreeLetterISOLanguageName;
        readonly static string[] _countryCodes;
        public string ConvertThreeLetterNameToTwoLetterName(string name)
        {
            if (name.Length != 3)
            {
                throw new ArgumentException("name must be three letters.");
            }

            name = name.ToUpper();

            CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
            foreach (CultureInfo culture in cultures)
            {
                RegionInfo region = new RegionInfo(culture.LCID);
                if (region.ThreeLetterISORegionName.ToUpper() == name)
                {
                    return region.TwoLetterISORegionName;
                }
            }

            return null;
        }
        static readonly float _fCtrVSVersion;
        static TemplatePattern()
        {
            try
            {
                _fCtrVSVersion = float.Parse(Utils.VSVersion);
            }
            catch
            {

                _fCtrVSVersion = 9.0F;
            }
            //see http://stackoverflow.com/questions/4884692/converting-country-codes-in-net
            CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
            _countryCodes = cultures.Select(culture =>
                {
                    try
                    {
                        var region = new RegionInfo(culture.LCID);
                        return region.ThreeLetterISORegionName;
                    }
                    catch (Exception ex)
                    {
                        Trace.TraceWarning("static TemplatePattern: Can't find 3 letter culture for {0}, exception:{1}", culture.EnglishName, ex.Message);
                        return null;
                    }
                }).Where(s => s != null).ToArray();

        }
        ILanguageProvider _languageProvider;
        static readonly string[] _descrExts = new string[] { ".htm", ".html", ".rtf", ".txt" };
        static readonly string[] _pictExts = new string[] { ".png", ".jpg", ".gif", ".bmp" };
        StorageType DefaultStorage = StorageType.FileSystem;
        IRepository _iRepository;
        public TemplatePattern(){}
        public void Init(ILanguageProvider languageProvider, XElement templateElem)
        //public TemplatePattern(ILanguageProvider languageProvider, XElement templateElem)
        {
            _languageProvider = languageProvider;

            if (_languageProvider != null)
            {
                //get defaultStorage from languageProvider
                ILanguageProviderMetaData ilpm = _languageProvider.GetAsService<ILanguageProviderMetaData>();
                DefaultStorage = (ilpm == null) ? StorageType.FileSystem : ilpm.SavedAs;
                _iRepository = _languageProvider.GetAsService<IRepository>();
            }
            if (templateElem == null || templateElem.Attribute("PatternName") == null
                || templateElem.Attribute("CodeLang") == null || templateElem.Name != "PatternTemplate")
            {
                return;//bad xml
            }
            PatternName = templateElem.Attribute("PatternName").Value;
            //_iRepository = this.GetAsService<IRepository>();
           // string filePicture = _iRepository.GetTemplatePath(PatternName);
         /*   switch (defaultStorage)
            {
                case StorageType.FileSystem:
                    filePicture = Path.Combine(_iRepository.TemplatesFolder, string.Format("{0}\\{1}", PatternName, PatternName));
                    break;
                case StorageType.Zip:
                    ILanguageProviderMetaData ilpmd = this.GetAsService<ILanguageProviderMetaData>();
                    filePicture = string.Format("{1}/{0}/{0}", PatternName, ilpmd.LPRepositoryName);
                    break;
                default:
                    throw new NotSupportedException(defaultStorage.ToString() + " storage type is not supportetd");
            }*/
            try
            {
                this.CodeLanguage = (CodeLangEnum)Enum.Parse(typeof(CodeLangEnum), templateElem.Attribute("CodeLang").Value, true);
            }
            catch
            {

                CodeLanguage = CodeLangEnum.Unsupported;
            }

            var properties = templateElem.Element("PatternProperties");
            PictureStoredAs = DefaultStorage;
            DescriptionStoredAs = DefaultStorage;
            Culture = "eng";
            MinVSVersion = _fCtrVSVersion;
            if (properties != null)
            {
                float minVSVer = _fCtrVSVersion;
                foreach (XElement e in properties.Elements())
                {
                    switch (e.Name.ToString())//.ToLower()
                    {
                        case "PatternAlias":
                            PatternAlias = e.Value;
                            break;
                        case "MinVSVersion":
                            if (!float.TryParse(e.Value, out minVSVer))
                                minVSVer = _fCtrVSVersion;
                            MinVSVersion = minVSVer;
                            break;
                        case "Picture":
                            //GetPicture(e);
                            _iRepository.GetPicture(this, e);

                            break;

                        case "OnlineResources":
                            OnlineResources = e.Value;
                            break;
                        case "CompilerSyntax":
                            CompilerSyntax = e.Value;
                            break;

                        case "ProgrammingStyle":
                            ProgrammingStyle = e.Value;
                            break;
                        case "Description":
                            //DescriptionStoredAs = e.Attr2Enum<StorageType>("StoredAs", StorageType.Text);
                            //Description = e.Value;
                            //GetDescription(e);
                            _iRepository.GetDescription(this,e);
                            break;

                        case "Category":
                            Category = e.Value;
                            break;
                        case "Culture":
                            Culture = e.Value;//"eng" if missing
                            break;

                        default:
                            break;



                    }
                }
            }
            //fill in with defaults

            if (Description == null)
            {//try adding default file if value not in xml file

                /*         //check for local language
                         if (Description == null)
                         {
                             Description = _descrExts.Select(e => string.Format("{0}_{1}{2}", filePicture, _currentCulture, e))
                                 .FirstOrDefault(s => File.Exists(s));
                         }
                         //check for Neutral language
                         if (Description == null)
                         {
                             Description = _descrExts.Select(e => filePicture + e).FirstOrDefault(s => File.Exists(s));
                         }
                         if (Description == null)
                         {
                             Description = string.Format("There is no no file or text description for the pattern '{0}'!", this.PatternName);
                             DescriptionStoredAs = StorageType.Text;
                         }*/
                //GetDescription(null);
                _iRepository.GetDescription(this, null);

            }
            if (Picture == null)
            {//try adding default file if value not in xml file
               // GetPicture(null);
                _iRepository.GetPicture(this, null);

            }
            IProjectInfo projectInfo = _languageProvider.GetAsService<IProjectInfo>();
            if (OnlineResources == null)
            {

                OnlineResources = string.Format(@"www.bing.com/search?q={0}+software+design+pattern+{1}", PatternName, projectInfo.CodeLang);
            }
            if (ProgrammingStyle == null)
            {

                ProgrammingStyle = "Plain";
            }
            if (PatternAlias == null)
            {

                PatternAlias = PatternName;
            }
            //add default File template xml element if missing
            var xmlFileArgs = templateElem.Element("FileTemplates");
            if (xmlFileArgs == null)
            {
                xmlFileArgs = new XElement("FileTemplates", GetDefaultFileTemplate());
                templateElem.Add(xmlFileArgs);
            }
            else
            {

                if (!xmlFileArgs.Elements().Any())
                {
                    XElement defaultElem = GetDefaultFileTemplate();
                    xmlFileArgs.Add(defaultElem);
                }
            }
            TemplateFiles = xmlFileArgs.Elements("FileTemplate").Select(e => new TemplateFile(this, e) as ITemplateFile);//.ToArray();

            var xmlargs = templateElem.Element("TemplateArguments");
            if (xmlargs == null)
            {
                xmlargs = new XElement("TemplateArgument", GetDefaultArgTemplate());
                templateElem.Add(xmlargs);
            }
            else
            {

                if (!xmlargs.Elements().Any())
                {
                    XElement defaultElem = this.GetDefaultArgTemplate();
                    xmlargs.Add(defaultElem);
                }
            }

            Arguments = xmlargs.Elements("TemplateArgument").Select(e =>
                {
                    if (e.Attribute("IsRuntime") == null || e.Attribute("IsRuntime").Value.ToLower() == "false")
                    {
                        return new TemplateArgument(this, e) as ITemplateArgument;
                    }
                    else
                    {
                        return new TemplateRuntimeArgument(this, projectInfo, e) as ITemplateArgument;
                    }
                });

        }
        /*
        private void GetPicture(XElement el)
        {
            if (el == null)
            {
                PictureStoredAs = DefaultStorage;
                Picture = null;
            }
            else
            {
                PictureStoredAs = el.Attr2Enum<StorageType>("StoredAs", DefaultStorage);
                Picture = el.Value;
            }
            switch (PictureStoredAs)
            {
                case StorageType.FileSystem:
                    if (el == null)
                    {
                       // _iRepository = this.GetAsService<IRepository>();
                        string filePicture = _iRepository.GetTemplatePath(PatternName);
                        Picture = _pictExts.Select(e => filePicture + e).FirstOrDefault(s => File.Exists(s));
                        if (Picture != null)
                            PictureStoredAs = DefaultStorage;
                    }
                    else
                    {
                        if (!Path.IsPathRooted(Picture))
                        {
                            Picture = Path.Combine(_iRepository.TemplatesFolder, string.Format("{0}\\{1}",
                                 PatternName, Picture));
                        }
                    }
                    break;
                case StorageType.Zip:
                    ILanguageProviderMetaData ilpmd = this.GetAsService<ILanguageProviderMetaData>();
                    if (el == null)
                    {
                        foreach (ZipEntry ze in _iRepository._zip.Where(z => z.FileName.ToLower().Contains(string.Format(@"{1}/{0}/{0}.", PatternName.ToLower(), ilpmd.LPRepositoryName.ToLower()))))
                        {
                            if (_pictExts.Any(end => ze.FileName.EndsWith(end, StringComparison.InvariantCultureIgnoreCase)))
                            {
                                Picture = ze.FileName;
                                break;
                            }
                        }
                    }
                    else
                    {
                        Picture = string.Format(@"{2}/{0}/{1}", PatternName, Picture, ilpmd.LPRepositoryName);
                    }


                    break;
                default:
                    throw new NotSupportedException(DefaultStorage.ToString() + " storage type is not supported for pictures");
            }
        }
        */
        /*
        private void GetDescription(XElement el)
        {
            if (el == null)
            {
                Description = null;
            }
            else
            {

                DescriptionStoredAs = el.Attr2Enum<StorageType>("StoredAs", DefaultStorage);
                Description = el.Value;
                if (DescriptionStoredAs == StorageType.Text)
                    return;
            }
            DescriptionStoredAs = DefaultStorage;
            switch (DefaultStorage)
            {
                case StorageType.FileSystem:
                    if (el == null)
                    {
                        string fileDescription = _iRepository.GetTemplatePath(PatternName);
                        Description = _descrExts.Select(e => string.Format("{0}_{1}{2}", fileDescription, _currentCulture, e))
                            .FirstOrDefault(s => File.Exists(s));
                        //check for Neutral language
                        if (Description == null)
                        {
                            Description = _descrExts.Select(e => fileDescription + e).FirstOrDefault(s => File.Exists(s));
                        }
                        if (Description == null)
                        {
                            Description = string.Format("There is no no file or text description for the pattern '{0}'!", this.PatternName);
                            DescriptionStoredAs = StorageType.Text;
                        }
                    }
                    else
                    {
                        if (!Path.IsPathRooted(Description))
                        {
                            Description = Path.Combine(_iRepository.TemplatesFolder, string.Format("{0}\\{1}",
                                 PatternName, Description));
                        }
                    }
                    break;
                case StorageType.Zip:
                    ILanguageProviderMetaData ilpmd = this.GetAsService<ILanguageProviderMetaData>();
                    if (el == null)
                    {
                       // BaseLanguageProvider blp = _languageProvider as BaseLanguageProvider;
                        //IRepository irep = _languageProvider.GetAsService<IRepository>();
                        foreach (ZipEntry ze in _iRepository._zip.Where(z => z.FileName.ToLower().Contains(string.Format(@"{1}/{0}/{0}.", PatternName.ToLower(), ilpmd.LPRepositoryName.ToLower()))))
                        {
                            if (_descrExts.Any(end => ze.FileName.EndsWith(end, StringComparison.InvariantCultureIgnoreCase)))
                            {
                                Description = ze.FileName;
                                break;
                            }
                        }
                    }
                    else
                    {
                        Description = string.Format(@"{2}/{0}/{1}", PatternName, Description,ilpmd.LPRepositoryName);
                    }


                    break;
                default:
                    throw new NotSupportedException(DefaultStorage.ToString() + " storage type is not supported for Description");
            }
        }
        */
        /*
        public Stream GetStreamFromLocation(string newlocation, StorageType st)
        {

            Stream str = null;

            switch (st)
            {

                case StorageType.FileSystem:
                    str = File.OpenRead(newlocation);
                    break;

                case StorageType.Zip:
                    //BaseLanguageProvider blp = _languageProvider as BaseLanguageProvider;
                    str = _languageProvider.GetStreamFromZip(newlocation);
                    break;

                default:
                    throw new NotImplementedException(DefaultStorage.ToString() + " format is not implemented for pictures");

            }
            return str;

        }
        */
        private XElement GetDefaultFileTemplate()
        {
            string fileItemName = null;
            string fileTemplate = null;
            switch (this.CodeLanguage)
            {
                case CodeLangEnum.CSharp:
                    fileTemplate = PatternName + "Template.cs";
                    fileItemName = PatternName + ".cs";
                    break;
                case CodeLangEnum.VB:
                    fileTemplate = PatternName + "Template.vb";
                    fileItemName = PatternName + ".vb";
                    break;
                case CodeLangEnum.ManagedCpp:
                case CodeLangEnum.NativeCpp:
                    fileTemplate = PatternName + "Template.h";// no .cpp for this template
                    fileItemName = PatternName + ".h";
                    break;
                default: return null;
            }
            XElement defaultElem = new XElement("FileTemplate", fileTemplate, new XAttribute("FileItemName", fileItemName)
                , new XAttribute("StoredAs", DefaultStorage.ToString())
                );
            return defaultElem;
        }

        private XElement GetDefaultArgTemplate()
        {
            XElement defaultElem = new XElement("TemplateArgument", new XAttribute("ArgumentName", "RootNamespace")
                , new XAttribute("IsRuntime", "True"), new XAttribute("Description", "Name Space")
                );
            return defaultElem;
        }

        public IEnumerable<ITemplateArgument> Arguments { get; set; }
        public float MinVSVersion { get; set; }// will filter features like optional params, async/await,dynamic
        public string Picture { get; set; }
        public StorageType PictureStoredAs { get; set; }
        public string OnlineResources { get; set; }
        public string CompilerSyntax { get; set; }//managed,native,VC10, empty
        public string ProgrammingStyle { get; set; }//generic,template,plain,nothing
        public string PatternName { get; set; }
        public string PatternAlias { get; set; }
        public string Description { get; set; }
        public StorageType DescriptionStoredAs { get; set; }
        public CodeLangEnum CodeLanguage { get; set; }//string version of CodeLanguageEnum
        public string Category { get; set; }//behavioral,creational
        public string Culture { get; set; }
        public IEnumerable<ITemplateFile> TemplateFiles { get; set; }
        #region IServiceProvider Members

        public object GetService(Type serviceType)
        {
            if (serviceType == typeof(ITemplatePattern) || serviceType == typeof(IServiceProvider))
                return this;
            if (serviceType == typeof(ILanguageProvider))
                return _languageProvider;
            object res = null;           
            res = (_languageProvider == null) ? null : _languageProvider.GetService(serviceType);
            return res;
        }

        #endregion
    }
}
